/**************************************************
|	File:		Enemy.cpp
|	Author:		Benjamin Budian
|	Course:		SGD
|	Purpose:	CEnemy is responsible for handling how enemy behavior
**************************************************/
#include "Enemy.h"

//Wrappers
#include "../SGD Wrappers/CSGD_TextureManager.h"
#include "../SGD Wrappers/CSGD_Direct3D.h"
#include "../SGD Wrappers/SGD_String.h"
#include "../SGD Wrappers/SGD_Math.h"
#include "../SGD Wrappers/CSGD_MessageSystem.h"
#include "../SGD Wrappers/CSGD_EventSystem.h"
#include "../SGD Wrappers/CSGD_XAudio2.h"

//Locals
#include "CellAnimation.h"
#include "Game.h"
#include "Player.h"
#include "DestroyEntityMessage.h"
#include "Projectile.h"
#include "MagicProjectile.h"
#include "SoundManager.h"

#define MOVEMENTCELLS	9
#define TOTALCOLUMNS	13
#define LOOPING			true
#define SPEED_SLOW		0.5f
#define SPEED_NORMAL	1.0f
#define SPEED_FAST		2.0f

CEnemy::CEnemy(void)
{
	m_eType = ENT_ENEMY;

	m_sHealth			= 0;
	m_sMana				= 0;
	m_nArmor			= 0;

	m_fDamage			= 0.0f;
	m_fSpeed			= 60.0f;

	m_nImageID			= -1;

	EnemyAnimation		= nullptr;
	WeaponAnimation		= nullptr;

	m_fWaypointX		= 0;	
	m_fWaypointY		= 0;
	m_bTravelingX		= false;	
	m_bTravelingY		= false;
	m_bTraveledX		= false;	
	m_bTraveledY		= false;
	m_bTraveling		= false;

	SetPosX(100);
	SetPosY(100);
	SetWidth(64);
	SetHeight(64);

	m_bTrackingTarget	= false;

	CSGD_EventSystem::GetInstance()->RegisterClient("ASSESS_THREAT", this);
	CSGD_EventSystem::GetInstance()->RegisterClient("DEBUG",this);
}

CEnemy::~CEnemy(void)
{
	CSGD_EventSystem::GetInstance()->UnregisterClientAll(this);

	SetTarget(nullptr);

	if(EnemyAnimation != nullptr)
		delete EnemyAnimation;
	if(WeaponAnimation != nullptr)
		delete WeaponAnimation;
}

void CEnemy::Initialize(int nEnemyType){
	//Debug mode default - false
	m_bDebug = false;

	//Set Enemy Type
	type = (CharacterTypes)nEnemyType;

	//Create animation system
	EnemyAnimation = new CCellAnimation();

	//Initialize image and animation system
	switch(nEnemyType){
	case SKELETON:
		m_nImageID = CSGD_TextureManager::GetInstance()->LoadTexture(_T("resource/images/Player/bb_skeleton.png"));
		EnemyAnimation->Initialize(64, 64, 13, 0, 13, false, _T("resource/images/Player/bb_skeleton.png"));
		break;
	case ORC:
		m_nImageID = CSGD_TextureManager::GetInstance()->LoadTexture(_T("resource/images/Player/bb_lpcfemaleorc.png"));
		EnemyAnimation->Initialize(64, 64, 13, 0, 13, false, _T("resource/images/Player/bb_lpcfemaleorc.png"));
		break;
	case HUMAN:
		m_nImageID = CSGD_TextureManager::GetInstance()->LoadTexture(_T("resource/images/Player/bb_tannedmale(resized).png"));
		EnemyAnimation->Initialize(64, 64, 13, 0, 13, false, _T("resource/images/Player/bb_tannedmale(resized).png"));
		break;
	}

	//Load Health bar image
	m_nHealthBarImgID = CSGD_TextureManager::GetInstance()->LoadTexture(_T("resource/images/Player/Health_Bar/bb_rad-rainbow-lifebar(resized).png"));

	//Set default attributes
	m_sHealth	= 100;
	m_sMana		= 100;
	m_fDamage	= 10.0f;

	//Initialize timers
	m_fFireTimerMax = 0.6f;
	m_fFireTimer = m_fFireTimerMax;

	//Get First waypoint
	GetNewWaypoint();
}

void CEnemy::Update( float fElapsedTime ) {

	HandleAI();

	m_fFireTimer -= fElapsedTime;

	EnemyAnimation->Update(fElapsedTime);
	CEntity::Update(fElapsedTime);

	StayInWorld();
}

void CEnemy::Render() {

	RECT rHealth; 
	rHealth.left = 0;
	rHealth.top = (7 - (m_sHealth/14)) * 8;
	rHealth.right = rHealth.left + 28;
	rHealth.bottom = rHealth.top + 8 ;

	CSGD_TextureManager::GetInstance()->Draw(m_nHealthBarImgID,(int)GetPosX(),(int)GetPosY(),1.0f,1.0f, &rHealth);

#pragma region DEBUG
	if(m_bDebug){
		TOSTRINGSTREAM toss;

		toss << "(" << GetPosX() << "," << GetPosY() << ")\n"
			<< "Health: " << m_sHealth << '\n'
			<< "Armor: " << m_nArmor << '\n'
			<< "Target: " << m_pTarget;

		CBitmapFont* pFont = CGame::GetInstance()->GetFont();

		pFont->Draw(toss.str().c_str(),(int)(GetPosX() + GetWidth()), (int)(GetPosY()), .3f,D3DCOLOR_ARGB(255,0,0,0));

		RECT rCollisionBox = GetRect();

		CSGD_Direct3D::GetInstance()->DrawRect(rCollisionBox, D3DCOLOR_ARGB(127,255,0,0));

		if(m_pTarget == nullptr)
			CSGD_Direct3D::GetInstance()->DrawLine((int)(GetPosX()+GetWidth()/2),(int)(GetPosY() + GetHeight()/2),(int)m_fWaypointX,(int)m_fWaypointY,D3DCOLOR_XRGB(0,255,0));
		else
			CSGD_Direct3D::GetInstance()->DrawLine((int)(GetPosX()+GetWidth()/2),(int)(GetPosY() + GetHeight()/2),(int)(m_pTarget->GetPosX() + m_pTarget->GetWidth()/2),(int)(m_pTarget->GetPosY() + m_pTarget->GetHeight()/2),D3DCOLOR_XRGB(0,255,0));
	}
#pragma endregion

	EnemyAnimation->Render((int)GetPosX(),(int)GetPosY(),1.0f,D3DCOLOR_ARGB(255,255,255,255));
}

void CEnemy::HandleAI(){

#pragma region Reset Travelling Variables
	if(m_pTarget == nullptr){
		if(m_bTrackingTarget == true){
			m_bTrackingTarget = false;
			m_bTravelingX = false;
			m_bTraveling = false;
			m_bTravelingY = false;
			m_bTraveledX = false;
			m_bTraveledY = false;
			SetVelX(0);
			SetVelY(0);
		}
#pragma endregion


#pragma region Travelling
		//Create a Waypoint Vector
		tVector2D vWaypoint = {
			m_fWaypointX - (GetPosX()+GetWidth()/2),
			m_fWaypointY - (GetPosY()+GetHeight()/2)
		};

		m_fTempWaypointX = vWaypoint.fX;
		m_fTempWaypointY = vWaypoint.fY;

		//Check if new waypoint is needed
		if(Vector2DLength(vWaypoint) <50){
			GetNewWaypoint();
			//Make a new vector?
		}

		//Determine Direction of travel so long as not currently traveling
		if( !m_bTraveling ){
			//x smaller than y
			if( vWaypoint.fX < vWaypoint.fY ){
				m_bTravelingX	= true;
				m_bTraveling	= true;
			}
			//y smaller than x
			else {
				m_bTravelingY	= true;
				m_bTraveling	= true;
			}
		}

		if( m_bTraveling ){
			if(m_bTravelingX){
				//Check if we've reached destination X
				if( (m_fWaypointX - (GetPosX() + GetWidth()/2) ) >= -1 && (m_fWaypointX-(GetPosX() + GetWidth()/2)) <= 1 ){
					SetVelX(0);
					m_bTravelingX	= false;
					m_bTraveledX	= true;
					m_bTravelingY	= true;
				}

				else if( ( m_fWaypointX - (GetPosX() + GetWidth()/2) ) < 0 ){
					//Traveling to the left
					SetVelX(-60);
					m_ePreviousMovementDirection = m_eMovementDirection;
					m_eMovementDirection = MOVE_LEFT;
					ChangeDirection();
				}
				else {
					//Traveling to the right
					SetVelX(60);
					m_ePreviousMovementDirection = m_eMovementDirection;
					m_eMovementDirection = MOVE_RIGHT;
					ChangeDirection();
				}
			}
			else if(m_bTravelingY){
				if( (  m_fWaypointY - (GetPosY() + GetHeight()/2))  >= -1 && (m_fWaypointY - (GetPosY() + GetHeight()/2)) <= 1 ){
					SetVelY(0);
					m_bTravelingY	= false;
					m_bTraveledY	= true;
					m_bTravelingX	= true;
				}
				else if( (  m_fWaypointY - (GetPosY() + GetHeight()/2))  < 0){
					//Traveling Up
					SetVelY(-60);
					m_ePreviousMovementDirection = m_eMovementDirection;
					m_eMovementDirection = MOVE_UP;
					ChangeDirection();
				}
				else {
					//Traveling down
					SetVelY(60);
					m_ePreviousMovementDirection = m_eMovementDirection;
					m_eMovementDirection = MOVE_DOWN;
					ChangeDirection();
				}
			}

			if( m_bTraveledX && m_bTraveledY ){
				m_bTraveling = false;
				m_bTraveledX = m_bTraveledY = false;
			}
		}
	}
#pragma endregion

#pragma region Target Tracking
	else {
		if(m_bTrackingTarget == false){
			m_bTrackingTarget = true;
			m_bTravelingX = false;
			m_bTraveling = false;
			m_bTravelingY = false;
			m_bTraveledX = false;
			m_bTraveledY = false;
			SetVelX(0);
			SetVelY(0);
		}

		tVector2D vToTarget = { (m_pTarget->GetPosX() + m_pTarget->GetWidth()/2) - (GetPosX()+GetWidth()/2), 
			(m_pTarget->GetPosY() + m_pTarget->GetHeight()/2) - (GetPosY()+GetHeight()/2)};

		if(Vector2DLength(vToTarget) > 400){
			SetTarget(nullptr);

			GetNewWaypoint();
		}

		if(Vector2DLength(vToTarget) < 150){
			RECT rSelf = GetRect();
			RECT rTarget = m_pTarget->GetRect();
			int nTargetCenterX = (rTarget.left + rTarget.right)/2;
			int nTargetCenterY = (rTarget.top + rTarget.bottom)/2;
			switch(m_eMovementDirection){
			case MOVE_UP:
				{
					tVector2D vOrientation = { 0, -1 };

					if( (nTargetCenterX > rSelf.left && nTargetCenterX < rSelf.right) && m_fFireTimer <= 0.0f){
						Fire(UP);
					}
				}
				break;
			case MOVE_DOWN:
				{
					tVector2D vOrientation = { 0, 1 };

					if((nTargetCenterX > rSelf.left && nTargetCenterX < rSelf.right) && m_fFireTimer <= 0.0f){
						Fire(DOWN);
					}
				}
				break;
			case MOVE_LEFT:
				{
					tVector2D vOrientation = { -1, 0 };

					if( (nTargetCenterY > rSelf.top && nTargetCenterY < rSelf.bottom) && m_fFireTimer <= 0.0f){
						Fire(LEFT);
					}
				}
				break;
			case MOVE_RIGHT:
				{
					tVector2D vOrientation = { 1, 0 };

					if( (nTargetCenterY > rSelf.top && nTargetCenterY < rSelf.bottom) && m_fFireTimer <= 0.0f){
						Fire(RIGHT);
					}
				}
				break;
			}
		}

		//Determine Direction of travel so long as not currently traveling
		if( !m_bTraveling ){
			//x smaller than y
			if( vToTarget.fX < vToTarget.fY ){
				m_bTravelingX	= true;
				m_bTraveling	= true;
			}
			//y smaller than x
			else {
				m_bTravelingY	= true;
				m_bTraveling	= true;
			}
		}

		if( m_bTraveling ){
			if(m_bTravelingX){
				//Check if we've reached destination X
				if( (vToTarget.fX) >= -1 && (vToTarget.fX) <= 1 ){
					SetVelX(0);
					m_bTravelingX	= false;
					m_bTraveledX	= true;
					m_bTravelingY	= true;
				}

				else if( ( vToTarget.fX) < 0 ){
					//Traveling to the left
					SetVelX(-60);
					m_ePreviousMovementDirection = m_eMovementDirection;
					m_eMovementDirection = MOVE_LEFT;
					ChangeDirection();
				}
				else {
					//Traveling to the right
					SetVelX(60);
					m_ePreviousMovementDirection = m_eMovementDirection;
					m_eMovementDirection = MOVE_RIGHT;
					ChangeDirection();
				}
			}
			else if(m_bTravelingY){
				if( (  vToTarget.fY)  >= -1 && (vToTarget.fY) <= 1 ){
					SetVelY(0);
					m_bTravelingY	= false;
					m_bTraveledY	= true;
					m_bTravelingX	= true;
				}
				else if( (  vToTarget.fY )  < 0){
					//Traveling Up
					SetVelY(-60);
					m_ePreviousMovementDirection = m_eMovementDirection;
					m_eMovementDirection = MOVE_UP;
					ChangeDirection();     
				}
				else {
					//Traveling down
					SetVelY(60);
					m_ePreviousMovementDirection = m_eMovementDirection;
					m_eMovementDirection = MOVE_DOWN;
					ChangeDirection();
				}
			}

			if( m_bTraveledX && m_bTraveledY ){
				m_bTraveling = false;
				m_bTraveledX = m_bTraveledY = false;
			}
		}
	}
#pragma endregion
}

void CEnemy::GetNewWaypoint(){
	int nMaxX = CGame::GetInstance()->GetScreenWidth() - 64;
	int nMinX = 64;
	int nMaxY = CGame::GetInstance()->GetScreenHeight() - 64;
	int nMinY = 64;

	m_fWaypointX = (float)( rand() %  (nMaxX - nMinX + 1)  + nMinX );
	m_fWaypointY = (float)( rand() %  (nMaxY - nMinY + 1)  + nMinY );
}

void CEnemy::HandleCollision(const IEntity* pOther) {
	switch(pOther->GetType()){

	case ENT_PLAYER:
		{
			CPlayer* pPlayer = (CPlayer*)(pOther);

			if(m_sHealth <= 0){
				CDestroyEntityMessage* pDestroyMsg = new CDestroyEntityMessage(this);
				CSGD_MessageSystem::GetInstance()->SendMsg(pDestroyMsg);
				pDestroyMsg = nullptr;

				CSGD_XAudio2::GetInstance()->SFXPlaySound(SoundManager::GetInstance()->GetSoundID(ENEMY_DEATH));
			}
		}
		break;
	case ENT_PROJECTILE:
		{
			CProjectile* pProjectile = (CProjectile*)(pOther);

			if(pProjectile->GetOwner()->GetType() == ENT_ENEMY)
				return;

			if( (pProjectile->GetDamage() - m_nArmor) >= 0 )
				m_sHealth -= (short)(pProjectile->GetDamage() - m_nArmor);

			if(m_sHealth <= 0){
				CDestroyEntityMessage* pDestroyMsg = new CDestroyEntityMessage(this);
				CSGD_MessageSystem::GetInstance()->SendMsg(pDestroyMsg);
				pDestroyMsg = nullptr;

				CSGD_XAudio2::GetInstance()->SFXPlaySound(SoundManager::GetInstance()->GetSoundID(ENEMY_DEATH));
			}
			else{
				CSGD_XAudio2::GetInstance()->SFXPlaySound(SoundManager::GetInstance()->GetSoundID(ENEMY_HIT));
				SetTarget(pProjectile->GetOwner());
			}
		}
		break;
	case ENT_MAGIC_PROJECTILE:
		{
			CProjectile* pProjectile = (CProjectile*)(pOther);

			if(pProjectile->GetOwner()->GetType() == ENT_ENEMY)
				return;

			m_sHealth -= (short)(pProjectile->GetDamage() - m_nArmor);

			if(m_sHealth <= 0){
				CDestroyEntityMessage* pDestroyMsg = new CDestroyEntityMessage(this);
				CSGD_MessageSystem::GetInstance()->SendMsg(pDestroyMsg);
				pDestroyMsg = nullptr;

				CSGD_XAudio2::GetInstance()->SFXPlaySound(SoundManager::GetInstance()->GetSoundID(ENEMY_DEATH));
			}
			else
				CSGD_XAudio2::GetInstance()->SFXPlaySound(SoundManager::GetInstance()->GetSoundID(ENEMY_HIT));
		}
		break;
	}
}

void CEnemy::StayInWorld(){
	if(GetPosX() <= 0)
		SetPosX(0);

	if(GetPosX() >= CGame::GetInstance()->GetScreenWidth() - GetWidth())
		SetPosX((float)(CGame::GetInstance()->GetScreenWidth() - GetWidth()));

	if(GetPosY() <= 32)
		SetPosY(32);

	if(GetPosY() >= CGame::GetInstance()->GetScreenHeight() - (GetHeight()+16))
		SetPosY((float)(CGame::GetInstance()->GetScreenHeight() - (GetHeight()+16)));
}

void CEnemy::ChangeDirection(){

	if(m_ePreviousMovementDirection != m_eMovementDirection){
		EnemyAnimation->SetAnimation(m_eMovementDirection,TOTALCOLUMNS,MOVEMENTCELLS,true);
		EnemyAnimation->Play(true, SPEED_NORMAL);
	}
}

void CEnemy::HandleEvent( CEvent* pEvent ){

	if(pEvent->GetEventID() == "GAME_OVER"){
		delete EnemyAnimation;
		delete WeaponAnimation;
		SetTarget(nullptr);
	}
	else if(pEvent->GetEventID() == "ASSESS_THREAT"){
		if(m_pTarget == nullptr){
			CEntity* pSender = reinterpret_cast< CEntity* >(pEvent->GetSender());

			tVector2D vToSender = { pSender->GetPosX() - GetPosX(), pSender->GetPosY() - GetPosY() };
			tVector2D vOrientation;
			switch(m_eMovementDirection){
			case MOVE_UP:
				vOrientation.fX = 0;
				vOrientation.fY = -1;
				break;
			case MOVE_DOWN:
				vOrientation.fX = 0;
				vOrientation.fY = 1;
				break;
			case MOVE_RIGHT:
				vOrientation.fX = 1;
				vOrientation.fY = 0;
				break;
			case MOVE_LEFT:
				vOrientation.fX = -1;
				vOrientation.fY = 0;
				break;
			default:
				vOrientation.fX = 0;
				vOrientation.fY = -1;
				break;
			}

			if( (Vector2DLength(vToSender) < 250)			&& 
				(DotProduct(vOrientation, vToSender) > 0.86f ))
			{
				SetTarget(pSender);
			}
		}
	}

	else if(pEvent->GetEventID() == "DEBUG"){
		m_bDebug = !m_bDebug;
	}

}

void CEnemy::SetTarget( CEntity* pTarget ){	

	if(m_pTarget != nullptr)
		m_pTarget->Release();

	m_pTarget = pTarget;

	if(m_pTarget != nullptr)
		m_pTarget->AddRef();

}

void CEnemy::Fire( Projectile_Directions eDirection){

	switch (type){
	case SKELETON:
		{
			CCreateProjectileMessage* pCreateProjectile = new CCreateProjectileMessage(this, eDirection, MAGIC);

			CSGD_MessageSystem::GetInstance()->SendMsg(pCreateProjectile);

			pCreateProjectile = nullptr;

			m_fFireTimer = m_fFireTimerMax;
		}
		break;
	case ORC:
		{
			CCreateProjectileMessage* pCreateProjectile = new CCreateProjectileMessage(this, eDirection, NORMAL);

			CSGD_MessageSystem::GetInstance()->SendMsg(pCreateProjectile);

			pCreateProjectile = nullptr;

			m_fFireTimer = m_fFireTimerMax;
		}
		break;
	case HUMAN:
		{
			CCreateProjectileMessage* pCreateProjectile = new CCreateProjectileMessage(this, eDirection, NORMAL);

			CSGD_MessageSystem::GetInstance()->SendMsg(pCreateProjectile);

			pCreateProjectile = nullptr;

			m_fFireTimer = m_fFireTimerMax;
		}
		break;
	}
	
}



